﻿using System;
using System.Collections.Generic;
using System.Linq;
using Borg;


public static class Program
{
    private static readonly Dictionary<string, string> ShortForms = new Dictionary<string, string>();

    static Program()
    {
        ShortForms.Add("ver", "version");
        ShortForms.Add("?", "help");
        ShortForms.Add("h", "help");
        ShortForms.Add("v", "verbosity");
        ShortForms.Add("q", "quiet");
        ShortForms.Add("m", "minimal");
        ShortForms.Add("n", "normal");
        ShortForms.Add("d", "detailed");
        ShortForms.Add("diag", "diagnostic");
        ShortForms.Add("dis", "discovery");
        ShortForms.Add("b", "base");
        ShortForms.Add("l", "logfile");
        ShortForms.Add("nl", "nologo");
        ShortForms.Add("args", "arguments");
        ShortForms.Add("cad", "createappdomain");
        ShortForms.Add("ext", "extensions");
        ShortForms.Add("trace", "trace");
    }

    private static Dictionary<string, string> GetArguments(IEnumerable<KeyValuePair<string, string>> keyValuePairs)
    {
        return keyValuePairs.ToDictionary(pair => pair.Key, pair => pair.Value);
    }

    private static KeyValuePair<string, string>[] GetArgumentsEx(Dictionary<string, string> options)
    {
        if (!options.ContainsKey("arguments"))
            return new KeyValuePair<string, string>[0];

        try
        {
            var args = options["arguments"].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            var argList = new List<KeyValuePair<string, string>>(args.Length);

            argList.AddRange(
                from option in args
                let sep = new[] { '=' }
                select option.Split(sep) into opt
                select new KeyValuePair<string, string>(opt[0], opt[1]));

            return argList.ToArray();
        }
        catch
        {
            throw new ArgumentException("invalid argument string.\n  args: " + options["arguments"]);
        }
    }

    [STAThread]
    public static void Main(string[] args)
    {
        Runtime.VersionInfo();

        var options = GetOptions(args);
        var argumentsEx = GetArgumentsEx(options);
        var arguments = GetArguments(argumentsEx);

        if (options.ContainsKey("version"))
            Console.WriteLine(Runtime.VersionInfo());

        else if (options.ContainsKey("help"))
        {
            Console.WriteLine(Runtime.VersionInfo());
            WriteHelp();
        }
        else
        {
            var noLogo = options.ContainsKey("nologo");

            if (!options.ContainsKey("ext") && !noLogo)
                Console.WriteLine(Runtime.VersionInfo());

            var verbosity = VerbosityLevel.Normal;

            if (options.ContainsKey("verbosity"))
                verbosity = (VerbosityLevel)Enum.Parse(typeof(VerbosityLevel), options["verbosity"], true);

            string[] discoveries = null;

            if (options.ContainsKey("discovery"))
                discoveries = options["discovery"].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

            var bases = !options.ContainsKey("base")
                ? new[] { AppDomain.CurrentDomain.BaseDirectory }
                : options["base"].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

            var logfile = options.ContainsKey("logfile")
                ? options["logfile"]
                : string.Empty;

            var createAppDomain = options.ContainsKey("createappdomain");
            var trace = options.ContainsKey("trace");

            if (options.ContainsKey("extension"))
            {
                var verbosityLevel = (BorgExt.VerbosityLevel)verbosity;

                Borg.Desktop.Initializer.Start(
                    discoveries,
                    bases,
                    argumentsEx,
                    verbosityLevel,
                    noLogo,
                    logfile,
                    trace,
                    createAppDomain);
            }
            else
                ClientRuntime.Create(
                    discoveries,
                    bases,
                    verbosity,
                    arguments,
                    logfile,
                    createAppDomain).Start();
        }
    }

    private static void PrintErrorAndQuit(string errorMessage)
    {
        var foregroundColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Red;

        Console.WriteLine(errorMessage);
        Console.ForegroundColor = foregroundColor;
        Console.WriteLine("Press ENTER to exit...");

        Console.ReadLine();
        Environment.Exit(1);
    }

    private static Dictionary<string, string> GetOptions(IEnumerable<string> args)
    {
        var options = new Dictionary<string, string>();

        foreach (var option in args)
        {
            int prefix;
            if (option.Length > 1)
                prefix = option.IndexOfAny(new[] { '/', '-' }, 0, 1) != 0 ? 1 : 0;
            else
                prefix = 1;

            if (prefix == 0)
            {
                var sep = option.Substring(1).Split(new[] { ':' });

                string item = (ShortForms.ContainsKey(sep[0])
                    ? ShortForms[sep[0]]
                    : sep[0]).ToLower();

                var opt = string.Empty;

                if (sep.Length > 1)
                    opt = ShortForms.ContainsKey(sep[1])
                        ? ShortForms[sep[1]]
                        : sep[1];

                options[item] = opt;

                if (!ShortForms.ContainsValue(item))
                    PrintErrorAndQuit(
                        opt != string.Empty
                        ? string.Format("Invalid start parameter: option \"/{0}:{1}\" not found", item, opt)
                        : string.Format("Invalid start parameter: option \"/{0}\" not found", item));
            }
            else
                PrintErrorAndQuit(
                    string.Format("Invalid start parameter: {0}\n  Parameter must start with '/' or '-'.", option));
        }

        if (options.ContainsKey("logfile") && string.IsNullOrEmpty(options["logfile"]))
            PrintErrorAndQuit("Filename of logfile must not be empty.");

        return options;
    }
    
    private static void WriteHelp()
    {
        Console.WriteLine("\r\n" +
                          "Syntax:              Borg.Runtime.exe [options]\r\n" +
                          "\r\n" +
                          "Description:         Launches the Borg runtime.\r\n" +
                          "\r\n" +
                          "Switches:\r\n" +
                          "\r\n" +
                          "  /help              Display this usage message. (Short form: /? or /h)\r\n" +
                          "\r\n" +
                          "  /nologo            Do not display the startup banner and copyright message.\r\n" +
                          "\r\n" +
                          "  /version           Display version information only. (Short form: /ver)\r\n" +
                          "\r\n" +
                          "  /process:<name>    Use process with given name.\r\n" +
                          "\r\n" +
                          "  /processid:<id>    Use process with given id.\r\n" +
                          "\r\n" +
                          "  /verbosity:<level> Display this amount of information in the event log.\r\n" +
                          "                     The available verbosity levels are: q[uiet], m[inimal],\r\n" +
                          "                     n[ormal], d[etailed], and diag[nostic]. (Short form: /v)\r\n" +
                          "                     Example:\r\n" +
                          "                       /verbosity:quiet\r\n" +
                          "\r\n" +
                          "  /discovery:<paths> Files and directories for initial discovery of contracts\r\n" +
                          "                     and plugins. Separate paths by ';'. Default are all \r\n" +
                          "                     assemblies in the base directory of the application. \r\n" +
                          "                     (Short form: /dis)\r\n" +
                          "                     Example:\r\n" +
                          "                       /dis:Borg.Assimilator.dll;contracts\\;plugins\\Monitor.dll;\r\n" +
                          "\r\n" +
                          "  /base:<paths>      Directories which are registered in the ApplicationBase to\r\n" +
                          "                     discover plugins and contracts from there. Separate paths\r\n" +
                          "                     by ';'. Default is the base directory of the application. \r\n" +
                          "                     Example:\r\n" +
                          "                       /base:.\\;plugins\\;contracts\\\r\n" +
                          "\r\n" +
                          "  /logfile:<path>    The Logger writes the messages in the specified file.\r\n" +
                          "                     Example:\r\n" +
                          "                       /logfile:.\\logfile.txt\r\n" +
                          "\r\n" +
                          "  /createAppDomain   Create a new app domain on start up.");
    }
}

